﻿// <copyright file="TBatchResponse.cs" company="N/A">
//     Copyright (c) Nicholas Barrett and Benjamin Kasten.  All rights reserved.
//
//     This program is free software; you can redistribute it and/or modify it
//     under the terms of the GNU General Public License as published by the
//     Free Software Foundation; either version 2 of the License, or (at your
//     option) any later version.
//
//     This program is distributed in the hope that it will be useful, but
//     WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
//     Public License for more details.
//
//     You should have received a copy of the GNU General Public License along
//     with this program; if not, write to the Free Software Foundation, 
//     Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// </copyright>

namespace BetterAzureTableStorage
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Net;
    using System.Runtime.Serialization;
    using System.Text;
    using System.Text.RegularExpressions;

    /// <summary>
    /// Provides a sub-response from a batched HttpWebResponse
    /// </summary>
    [Serializable]
    public class TBatchResponse : WebResponse
    {
        /// <summary>
        /// Backing field for the GetResponseStream method.
        /// </summary>
        private MemoryStream fResponseStream;

        /// <summary>
        /// Backing field for Headers
        /// </summary>
        private WebHeaderCollection fHeaders = new WebHeaderCollection();

        /// <summary>
        /// Backing field for Content
        /// </summary>
        private string fContent;

        /// <summary>
        /// Gets or sets the status code.
        /// </summary>
        /// <value>The status code.</value>
        public HttpStatusCode StatusCode { get; set; }

        /// <summary>
        /// Gets or sets the status description.
        /// </summary>
        /// <value>The status description.</value>
        public string StatusDescription { get; set; }

        /// <summary>
        /// Gets or sets the content.
        /// </summary>
        /// <value>The content.</value>
        public string Content
        {
            get
            {
                return this.fContent;
            }

            set
            {
                this.fContent = value;
                this.fResponseStream = new MemoryStream(Encoding.UTF8.GetBytes(this.fContent));
            }
        }

        /// <summary>
        /// Gets or sets the content ID.
        /// </summary>
        /// <value>The content ID.</value>
        public int ContentID { get; set; }

        /// <summary>
        /// When overridden in a derived class, gets a collection of header name-value pairs associated with this request.
        /// </summary>
        /// <value></value>
        /// <returns>An instance of the <see cref="T:System.Net.WebHeaderCollection"/> class that contains header values associated with this response.</returns>
        /// <exception cref="T:System.NotSupportedException">Any attempt is made to get or set the property, when the property is not overridden in a descendant class. </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        /// </PermissionSet>
        public override WebHeaderCollection Headers
        {
            get
            {
                return this.fHeaders;
            }
        }

        /// <summary>
        /// When overridden in a derived class, gets the URI of the Internet resource that actually responded to the request.
        /// </summary>
        /// <value></value>
        /// <returns>An instance of the <see cref="T:System.Uri"/> class that contains the URI of the Internet resource that actually responded to the request.</returns>
        /// <exception cref="T:System.NotSupportedException">Any attempt is made to get or set the property, when the property is not overridden in a descendant class. </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        /// </PermissionSet>
        public override Uri ResponseUri
        {
            get
            {
                return null;
            }
        }

        /// <summary>
        /// When overridden in a descendant class, gets or sets the content length of data being received.
        /// </summary>
        /// <value></value>
        /// <returns>The number of bytes returned from the Internet resource.</returns>
        /// <exception cref="T:System.NotSupportedException">Any attempt is made to get or set the property, when the property is not overridden in a descendant class. </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        /// </PermissionSet>
        public override long ContentLength
        {
            get
            {
                if (this.fResponseStream == null)
                {
                    return 0L;
                }
                else
                {
                    return this.fResponseStream.Length;
                }
            }

            set
            {
            }
        }

        /// <summary>
        /// When overridden in a derived class, gets or sets the content type of the data being received.
        /// </summary>
        /// <value></value>
        /// <returns>A string that contains the content type of the response.</returns>
        /// <exception cref="T:System.NotSupportedException">Any attempt is made to get or set the property, when the property is not overridden in a descendant class. </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        /// </PermissionSet>
        public override string ContentType
        {
            get
            {
                return this.Headers["Content-Type"];
            }

            set
            {
                this.Headers["Content-Type"] = value;
            }
        }

        /// <summary>
        /// Gets or sets the response header pattern.
        /// </summary>
        /// <value>The response header pattern.</value>
        /// <remarks>
        /// Matches the ultimate header of the http response.
        /// </remarks>
        private static Regex ResponsePattern { get; set; }

        /// <summary>
        /// Gets or sets the header pattern.
        /// </summary>
        /// <remarks>
        /// Matches a response "header: value" line.
        /// </remarks>
        /// <value>The header pattern.</value>
        private static Regex HeaderPattern { get; set; }

        /// <summary>
        /// Initializes static members of the TBatchResponse class.
        /// </summary>
        static TBatchResponse()
        {
            HeaderPattern = new Regex(@"^(?<Name>[^\:]+)\: (?<Value>.*)");
            ResponsePattern = new Regex(@"^HTTP/(?<Version>1\.1) (?<StatusCode>\d+) (?<StatusText>.*)");
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TBatchResponse"/> class.
        /// </summary>
        public TBatchResponse()
        {
        }

        /// <summary>
        /// Gets a TBatchResponse from a data stream.
        /// </summary>
        /// <param name="data">The data stream.</param>
        /// <returns>A TBatchResponse created from <paramref name="data"/></returns>
        public static TBatchResponse FromStream(Stream data)
        {
            StreamReader reader = new StreamReader(data);
            string line;

            TBatchResponse br = new TBatchResponse();

            try
            {
                while (!string.IsNullOrEmpty(line = reader.ReadLine()))
                {
                }

                while ((line = reader.ReadLine()) != null)
                {
                    Match match = ResponsePattern.Match(line);
                    if (match.Success)
                    {
                        br.StatusCode = (HttpStatusCode)int.Parse(match.Groups["StatusCode"].Value);
                        br.StatusDescription = match.Groups["StatusText"].Value;
                        continue;
                    }

                    match = HeaderPattern.Match(line);
                    if (match.Success)
                    {
                        string name = match.Groups["Name"].Value;
                        string value = match.Groups["Value"].Value;

                        br.Headers[name] = value;

                        if (name == "Content-ID")
                        {
                            br.ContentID = int.Parse(value);
                        }
                    }

                    if (String.IsNullOrEmpty(line))
                    {
                        br.Content = reader.ReadToEnd();
                        break;
                    }
                }
            }
            finally
            {
                if (br != null)
                {
                    br.Close();
                }
            }

            return br;
        }

        /// <summary>
        /// Gets the sub-responses from the content of a batch response.
        /// </summary>
        /// <param name="response">The response.</param>
        /// <returns>
        /// The sub-responses from the content of <paramref name="response"/>
        /// </returns>
        public static IList<TBatchResponse> FromHttpWebResponse(HttpWebResponse response)
        {
            List<TBatchResponse> responses = new List<TBatchResponse>();

            if (response.StatusCode != HttpStatusCode.Accepted)
            {
                throw new TTableStorageException("Invalid response.");
            }

            StreamReader reader = new StreamReader(response.GetResponseStream());
            string line;

            MemoryStream ms = null;
            StreamWriter writer = null;

            ////string debug = reader.ReadToEnd();
            ////StringReader reader2 = new StringReader(debug);

            try
            {
                while ((line = reader.ReadLine()) != null)
                {
                    if (line.StartsWith("--changesetresponse"))
                    {
                        if (ms != null)
                        {
                            writer.Flush();
                            ms.Seek(0L, SeekOrigin.Begin);
                            responses.Add(FromStream(ms));

                            writer.Close();
                            writer = null;
                            ms = null;
                        }

                        ms = new MemoryStream();
                        writer = new StreamWriter(ms);
                        continue;
                    }

                    if (ms != null)
                    {
                        writer.WriteLine(line);
                    }
                }
            }
            finally
            {
                if (ms != null)
                {
                    ms.Close();
                }
            }

            return responses;
        }

        /// <summary>
        /// When overridden in a descendant class, returns the data stream from the Internet resource.
        /// </summary>
        /// <returns>
        /// An instance of the <see cref="T:System.IO.Stream"/> class for reading data from the Internet resource.
        /// </returns>
        /// <exception cref="T:System.NotSupportedException">Any attempt is made to access the method, when the method is not overridden in a descendant class. </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        /// </PermissionSet>
        public override Stream GetResponseStream()
        {
            return this.fResponseStream;
        }

        /// <summary>
        /// When overridden by a descendant class, closes the response stream.
        /// </summary>
        /// <exception cref="T:System.NotSupportedException">Any attempt is made to access the method, when the method is not overridden in a descendant class. </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        /// </PermissionSet>
        public override void Close()
        {
            if (this.fResponseStream != null)
            {
                this.fResponseStream.Close();
            }
        }

        /// <summary>
        /// Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with the data that is needed to serialize the target object.
        /// </summary>
        /// <param name="serializationInfo">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> to populate with data.</param>
        /// <param name="streamingContext">A <see cref="T:System.Runtime.Serialization.StreamingContext"/> that specifies the destination for this serialization.</param>
        [ExcludeFromCodeCoverage]
        protected override void GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext)
        {
            base.GetObjectData(serializationInfo, streamingContext);
        }
    }
}
